package com.thz.system.service.gif.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.thz.common.constants.ConversionConstants;
import com.thz.common.dto.gif.ConversionVideoToGifRequestDTO;
import com.thz.common.dto.gif.ConversionVideoToGifResponseDTO;
import com.thz.common.dto.user.UserInfoDTO;
import com.thz.common.entity.ConversionRecord;
import com.thz.common.entity.ConversionRecordVideoToGif;
import com.thz.common.enums.ConversionStatus;
import com.thz.common.enums.ConversionType;
import com.thz.common.enums.ErrorCode;
import com.thz.common.exception.BusinessException;
import com.thz.common.utils.FileUtils;
import com.thz.system.config.CosConfig;
import com.thz.system.mapper.gif.ConversionVideoToGifMapper;
import com.thz.system.service.gif.IConversionCacheService;
import com.thz.system.service.gif.IConversionVideoToGifService;
import com.thz.system.service.record.IConversionRecordService;
import com.thz.system.service.user.IUserInfoService;
import com.thz.system.utils.FFmpegUtils;
import com.thz.system.utils.GIFCompressorUtils;
import com.thz.system.utils.cos.CosFileUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;

import java.util.stream.Collectors;

/**
 * 视频转换服务实现类
 * 
 * @author THZ
 * @date 2025
 */
@Slf4j
@Service
public class ConversionVideoToGifServiceImpl implements IConversionVideoToGifService {
    
    // 2C4G 机器并发保护，限制同一时间仅1个转码任务，避免系统卡顿
    private static final Semaphore CONVERT_SEMAPHORE = new Semaphore(1);
    
    @Autowired
    private ConversionVideoToGifMapper conversionRecordMapper;
    
    @Autowired
    private IUserInfoService userInfoService;

    @Autowired
    private IConversionRecordService conversionRecordService;
    
    @Autowired
    private CosFileUtils cosFileUtils;
    
    @Autowired
    private CosConfig cosConfig;

    @Autowired
    private IConversionCacheService cacheService;
    
    @Value("${app.domain:http://localhost:9091}")
    private String serverDomain;
    
    @Override
    public ConversionVideoToGifResponseDTO convertVideoToGif(MultipartFile file, ConversionVideoToGifRequestDTO requestDTO, String ipAddress) {
        // 并发限流保护
        boolean acquired = false;
        try {
            acquired = CONVERT_SEMAPHORE.tryAcquire();
            if (!acquired) {
                acquired = CONVERT_SEMAPHORE.tryAcquire(60, java.util.concurrent.TimeUnit.SECONDS);
                if (!acquired) {
                    throw new BusinessException(ErrorCode.SERVICE_ERROR, "当前任务较多，请稍后重试");
                }
            }
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new BusinessException(ErrorCode.SERVICE_ERROR, "系统繁忙，请稍后重试");
        }
        // 1. 验证文件
        FileUtils.validateVideoFile(file);
        
        // 2. 参数校验和修正（双重保险）
        requestDTO.setFps(ConversionConstants.clampFpsForServer(requestDTO.getFps()));
        requestDTO.setQuality(ConversionConstants.validateQuality(requestDTO.getQuality()));
        requestDTO.setDuration(ConversionConstants.clampDurationForServer(requestDTO.getDuration()));
        // 限制默认与最大尺寸，避免高分辨率占满CPU
        if (requestDTO.getWidth() == null && requestDTO.getHeight() == null) {
            requestDTO.setWidth(ConversionConstants.SERVER_DEFAULT_MAX_WIDTH);
            requestDTO.setHeight(null);
        } else {
            if (requestDTO.getWidth() != null && requestDTO.getWidth() > ConversionConstants.SERVER_DEFAULT_MAX_WIDTH) {
                requestDTO.setWidth(ConversionConstants.SERVER_DEFAULT_MAX_WIDTH);
                requestDTO.setHeight(null); // 宽度受限时，高度设为null让FFmpeg自动计算
            }
            if (requestDTO.getHeight() != null && requestDTO.getHeight() > ConversionConstants.SERVER_DEFAULT_MAX_HEIGHT) {
                requestDTO.setHeight(ConversionConstants.SERVER_DEFAULT_MAX_HEIGHT);
                requestDTO.setWidth(null); // 高度受限时，宽度设为null让FFmpeg自动计算
            }
        }
        
        log.info("参数校验后 - width: {}, height: {}, fps: {}, quality: {}", 
                requestDTO.getWidth(), requestDTO.getHeight(), requestDTO.getFps(), requestDTO.getQuality());
        
        // 【性能优化】直接保存到本地临时目录，避免先上传再下载（节省100+秒）
        long totalStartTime = System.currentTimeMillis();
        long saveStartTime = System.currentTimeMillis();
        String tempPath = System.getProperty("java.io.tmpdir") + "/video_" + System.currentTimeMillis() + "." + FileUtils.getFileExtension(file.getOriginalFilename());
        File videoFile = new File(tempPath);
        long saveTime = 0;
        try {
            file.transferTo(videoFile);
            saveTime = System.currentTimeMillis() - saveStartTime;
            log.info("视频文件已保存到本地临时目录: {}, 耗时={}ms", tempPath, saveTime);
        } catch (IOException e) {
            log.error("保存视频文件到本地失败", e);
            throw new BusinessException(ErrorCode.FILE_SAVE_FAILED, "保存视频文件失败: " + e.getMessage(), e);
        }
        
        // 原始视频文件路径（如果需要保存，后续异步上传）
        String videoFilePath = null;
        
        // 3.5 获取或创建用户
        UserInfoDTO userInfoDTO = userInfoService.getOrCreateUser(ipAddress);
        
        // 4. 创建转换记录（存储COS URL或本地路径）
        ConversionRecordVideoToGif record = new ConversionRecordVideoToGif();
        record.setOriginalFilename(file.getOriginalFilename());
        record.setOriginalFilePath(videoFilePath != null ? videoFilePath : "");
        record.setOriginalFileSize(file.getSize());
        record.setConversionStatus(ConversionStatus.PENDING.getCode());
        record.setIpAddress(ipAddress);
        record.setUserId(userInfoDTO.getId());
        record.setFps(requestDTO.getFps() != null ? requestDTO.getFps() : 10);
        record.setThumbnailPath(null);
        record.setCreateTime(LocalDateTime.now());
        record.setUpdateTime(LocalDateTime.now());
        
        conversionRecordMapper.insert(record);
        
        try {
            // 5. 更新状态为转换中
            record.setConversionStatus(ConversionStatus.CONVERTING.getCode());
            conversionRecordMapper.updateById(record);
            
            // 6. 获取视频时长
            Double duration = FFmpegUtils.getVideoDuration(videoFile.getAbsolutePath());
            record.setVideoDuration(duration);
            
            // 7. 生成GIF文件名和路径（使用系统临时目录）
            String gifFilename = FileUtils.generateUniqueFilename(file.getOriginalFilename()).replace(
                    FileUtils.getFileExtension(file.getOriginalFilename()), "gif");
            // 使用系统临时目录，无需配置
            String tempDir = System.getProperty("java.io.tmpdir");
            File gifFile = new File(tempDir, gifFilename);
            
            // 7.5 生成缩略图文件名和路径
            String thumbnailFilename = gifFilename.replace(".gif", "_thumb.jpg");
            File thumbnailFile = new File(tempDir, thumbnailFilename);
            
            // 8. 执行GIF转换（最耗时的任务，先执行以获得全部CPU资源）
            long gifStartTime = System.currentTimeMillis();
            FFmpegUtils.convertVideoToGif(
                    videoFile.getAbsolutePath(),
                    gifFile.getAbsolutePath(),
                    requestDTO.getWidth(),
                    requestDTO.getHeight(),
                    requestDTO.getFps(),
                    requestDTO.getStartTime(),
                    requestDTO.getDuration(),
                    requestDTO.getQuality(),
                    requestDTO.getCropX(),
                    requestDTO.getCropY(),
                    requestDTO.getCropWidth(),
                    requestDTO.getCropHeight()
            );
            long gifTime = System.currentTimeMillis() - gifStartTime;

            // GIF压缩：仅调用静态 gifsicle 工具，保循环和动画
            try {
                long sizeBefore = gifFile.length();
                int targetColors;
                int lossyLevel;
                if (sizeBefore > 15 * 1024 * 1024) {
                    targetColors = 32;
                    lossyLevel = 80;
                } else if (sizeBefore > 5 * 1024 * 1024) {
                    targetColors = 48;
                    lossyLevel = 30;
                } else {
                    targetColors = 64;
                    lossyLevel = 0;
                }
                // 1. 备份源GIF
                String gifBackup = gifFile.getAbsolutePath() + ".orig";
                gifFile.renameTo(new File(gifBackup));
                File backup = new File(gifBackup);
                File output = gifFile;
                boolean compressed = false;
                try {
                    GIFCompressorUtils.compressGifWithGifsicle(backup, output, targetColors, lossyLevel);
                    compressed = true;
                } catch(Exception ex) {
                    log.warn("gifsicle 压缩失败或未安装，使用原ffmpeg GIF: {}", ex.toString());
                    java.nio.file.Files.copy(backup.toPath(), output.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
                }
                backup.delete();
                long sizeAfter = gifFile.length();
                log.info("GIF最终压缩: {} → {} (压缩率: {}%) 目标色: {} lossy:{}", sizeBefore, sizeAfter, sizeBefore > 0 ? (100 - sizeAfter * 100.0 / sizeBefore) : 0, targetColors, lossyLevel);
            } catch (Exception e) {
                log.error("GIF压缩(fallback到ffmpeg输出)失败，继续后续流程。", e);
            }
            
            // 9. 获取GIF文件信息
            long gifSize = gifFile.length();
            int[] dimensions = FFmpegUtils.getImageDimensions(gifFile.getAbsolutePath());
            // 验证尺寸是否有效
            if (dimensions == null || dimensions.length != 2 || dimensions[0] <= 0 || dimensions[1] <= 0) {
                log.warn("GIF尺寸获取失败或无效，尝试从实际GIF文件读取...");
                // 尝试从实际GIF文件读取尺寸（使用ImageIO）
                try {
                    javax.imageio.ImageReader reader = javax.imageio.ImageIO.getImageReadersByFormatName("gif").next();
                    reader.setInput(javax.imageio.ImageIO.createImageInputStream(gifFile));
                    int width = reader.getWidth(0);
                    int height = reader.getHeight(0);
                    dimensions = new int[]{width, height};
                    log.info("从GIF文件读取尺寸成功: {}x{}", width, height);
                } catch (Exception e) {
                    log.error("从GIF文件读取尺寸失败，使用默认值", e);
                    // 如果都失败了，使用请求参数中的尺寸（如果有）
                    dimensions = new int[]{
                        requestDTO.getWidth() != null ? requestDTO.getWidth() : 0,
                        requestDTO.getHeight() != null ? requestDTO.getHeight() : 0
                    };
                }
            }
            log.info("GIF转换成功: 尺寸={}x{}, 文件大小={} bytes, 耗时={}ms",
                    dimensions[0], dimensions[1], gifSize, gifTime);
            
            // 10. 提取缩略图（很快，1-2秒）
            long thumbnailStartTime = System.currentTimeMillis();
            Integer thumbnailWidth = 400;
            Double startTime = requestDTO.getStartTime();
            FFmpegUtils.extractFrameAtTime(
                    videoFile.getAbsolutePath(),
                    thumbnailFile.getAbsolutePath(),
                    thumbnailWidth,
                    null,
                    startTime
            );
            long thumbnailTime = System.currentTimeMillis() - thumbnailStartTime;
            log.info("缩略图提取成功: 时间点={}秒, 文件={}, 耗时={}ms",
                    startTime, thumbnailFile.getAbsolutePath(), thumbnailTime);
            
            // 11. 【性能优化】异步上传策略：只等待缩略图，GIF后台异步上传
            // 这样用户可以在3-5秒内看到结果，而不是等待2-3分钟
            long uploadStartTime = System.currentTimeMillis();
            
            long gifSizeMB = gifFile.length() / (1024 * 1024);
            long gifSizeBytes = gifFile.length();
            log.info("开始上传 - GIF大小: {}MB ({} bytes), 策略: 缩略图同步上传，GIF异步上传",
                    gifSizeMB, gifSizeBytes);
            
            // 先同步上传缩略图（很快，2秒内完成）
            String thumbnailPath;
            try {
                log.info("开始上传缩略图文件: {}", thumbnailFile.getName());
                thumbnailPath = cosFileUtils.uploadLocalFile(thumbnailFile, cosConfig.getOutputPrefix());
                thumbnailFile.delete();
                long thumbnailUploadTime = System.currentTimeMillis() - uploadStartTime;
                log.info("缩略图已上传到COS: {}, 耗时={}ms", thumbnailPath, thumbnailUploadTime);
            } catch (Exception e) {
                log.error("缩略图上传失败: {}", thumbnailFile.getName(), e);
                throw new BusinessException(ErrorCode.FILE_UPLOAD_FAILED, 
                    "缩略图上传失败: " + e.getMessage(), e);
            }
            
            // GIF异步上传（不阻塞响应，后台进行）
            CompletableFuture<String> gifUploadFuture = CompletableFuture.supplyAsync(() -> {
                // 【优化】添加重试机制，最多重试2次
                int maxRetries = 2;
                int retryCount = 0;
                Exception lastException = null;
                long gifUploadStartTime = System.currentTimeMillis();
                
                while (retryCount <= maxRetries) {
                    try {
                        if (retryCount > 0) {
                            log.warn("GIF上传重试 - 第{}次重试, 文件: {}, 大小: {}MB",
                                    retryCount, gifFile.getName(), gifSizeMB);
                            // 重试前等待2秒
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ie) {
                                Thread.currentThread().interrupt();
                                throw new RuntimeException("重试等待被中断", ie);
                            }
                        } else {
                            log.info("开始异步上传GIF文件: {}, 大小: {}MB", gifFile.getName(), gifSizeMB);
                        }
                        
                        String path = cosFileUtils.uploadLocalFile(gifFile, cosConfig.getOutputPrefix());
                        // 上传成功后再删除文件
                        if (gifFile.exists()) {
                            gifFile.delete();
                            log.info("GIF文件已删除: {}", gifFile.getAbsolutePath());
                        }
                        long gifUploadTime = System.currentTimeMillis() - gifUploadStartTime;
                        log.info("GIF异步上传完成: {}, 耗时={}ms ({}秒)", 
                                path, gifUploadTime, String.format("%.2f", gifUploadTime / 1000.0));
                        return path;
                    } catch (Exception e) {
                        lastException = e;
                        retryCount++;
                        log.error("GIF上传失败 (尝试 {}/{}): {}", retryCount, maxRetries + 1, 
                                gifFile.getName(), e);
                        
                        if (retryCount > maxRetries) {
                            log.error("GIF上传最终失败，已重试{}次: {}", maxRetries, gifFile.getName());
                            throw new RuntimeException("GIF上传失败，已重试" + maxRetries + "次: " + e.getMessage(), e);
                        }
                    }
                }
                
                // 理论上不会到达这里，但为了编译通过
                throw new RuntimeException("GIF上传失败: " + (lastException != null ? lastException.getMessage() : "未知错误"));
            });
            
            // 先使用临时路径（本地文件路径），GIF上传完成后异步更新
            String gifPath = "temp://" + gifFile.getAbsolutePath();
            log.info("GIF文件已标记为异步上传，先返回临时路径，后台上传完成后将更新为COS URL");
            
            // 异步更新：GIF上传完成后更新记录
            gifUploadFuture.thenAcceptAsync(cosPath -> {
                try {
                    log.info("GIF异步上传完成，更新转换记录 - 记录ID: {}, COS路径: {}", record.getId(), cosPath);
                    
                    // 1. 更新子表记录
                    record.setGifFilePath(cosPath);
                    record.setUpdateTime(LocalDateTime.now());
                    conversionRecordMapper.updateById(record);
                    log.info("转换记录已更新 - 记录ID: {}, GIF路径: {}", record.getId(), cosPath);
                    
                    // 2. 更新统一记录表（重要：历史记录查询依赖此表）
                    ConversionRecord unifiedRecord = conversionRecordService.findByReferenceId(
                            record.getId(), ConversionType.VIDEO_TO_GIF.getCode());
                    if (unifiedRecord != null) {
                        unifiedRecord.setOutputFilename(cosPath);  // 更新为COS URL
                        unifiedRecord.setUpdateTime(LocalDateTime.now());
                        conversionRecordService.updateRecord(unifiedRecord);
                        log.info("统一转换记录表已更新 - 记录ID: {}, GIF路径: {}", unifiedRecord.getId(), cosPath);
                    } else {
                        log.warn("未找到统一转换记录 - 记录ID: {}, 类型: {}, 尝试创建", 
                                record.getId(), ConversionType.VIDEO_TO_GIF.getCode());
                        // 如果统一记录不存在，创建一个（可能是在创建统一记录之前就完成了上传）
                        unifiedRecord = new ConversionRecord();
                        unifiedRecord.setUserId(record.getUserId());
                        unifiedRecord.setIpAddress(record.getIpAddress());
                        unifiedRecord.setConversionType(ConversionType.VIDEO_TO_GIF.getCode());
                        unifiedRecord.setReferenceId(record.getId());
                        unifiedRecord.setOriginalFilename(record.getOriginalFilename());
                        unifiedRecord.setOutputFilename(cosPath);
                        unifiedRecord.setThumbnailFilename(record.getThumbnailPath());
                        unifiedRecord.setOriginalFileSize(record.getOriginalFileSize());
                        unifiedRecord.setOutputFileSize(record.getGifFileSize());
                        unifiedRecord.setOutputWidth(record.getGifWidth());
                        unifiedRecord.setOutputHeight(record.getGifHeight());
                        unifiedRecord.setConversionStatus(record.getConversionStatus());
                        unifiedRecord.setCreateTime(record.getCreateTime());
                        unifiedRecord.setUpdateTime(LocalDateTime.now());
                        conversionRecordService.createRecord(unifiedRecord);
                        log.info("创建新的统一转换记录 - 记录ID: {}, GIF路径: {}", unifiedRecord.getId(), cosPath);
                    }
                } catch (Exception e) {
                    log.error("更新转换记录失败 - 记录ID: {}, COS路径: {}", record.getId(), cosPath, e);
                }
            }).exceptionally(ex -> {
                log.error("GIF异步上传失败 - 记录ID: {}", record.getId(), ex);
                return null;
            });
            
            // 10. 更新转换记录（存储COS URL或本地路径）
            record.setGifFilename(gifFilename);
            record.setThumbnailFilename(thumbnailFilename);
            record.setGifFilePath(gifPath);
            record.setGifFileSize(gifSize);
            record.setThumbnailPath(thumbnailPath);
            record.setGifWidth(dimensions[0]);
            record.setGifHeight(dimensions[1]);
            record.setConversionStatus(ConversionStatus.SUCCESS.getCode());
            record.setUpdateTime(LocalDateTime.now());
            
            conversionRecordMapper.updateById(record);
            
            // 12. 清理临时视频文件
            if (videoFile.exists()) {
                videoFile.delete();
                log.info("临时视频文件已删除: {}", videoFile.getAbsolutePath());
            }
            
            // 13. 记录到统一转换记录表
            ConversionRecord unifiedRecord = new ConversionRecord();
            unifiedRecord.setUserId(userInfoDTO.getId());
            unifiedRecord.setIpAddress(ipAddress);
            unifiedRecord.setConversionType(ConversionType.VIDEO_TO_GIF.getCode());
            unifiedRecord.setReferenceId(record.getId());
            unifiedRecord.setOriginalFilename(record.getOriginalFilename());
            unifiedRecord.setOutputFilename(record.getGifFilePath());
            unifiedRecord.setThumbnailFilename(record.getThumbnailPath());
            unifiedRecord.setOriginalFileSize(record.getOriginalFileSize());
            unifiedRecord.setOutputFileSize(record.getGifFileSize());
            unifiedRecord.setOutputWidth(record.getGifWidth());
            unifiedRecord.setOutputHeight(record.getGifHeight());
            unifiedRecord.setConversionStatus(record.getConversionStatus());
            unifiedRecord.setCreateTime(record.getCreateTime());
            unifiedRecord.setUpdateTime(record.getUpdateTime());
            conversionRecordService.createRecord(unifiedRecord);
            
            // 14. 更新用户转换次数
            userInfoService.incrementConversions(ipAddress);

            // 15. 返回结果
            return buildResponseDTO(record);
            
        } catch (Exception e) {
            log.error("视频转换失败", e);
            
            // 清理临时视频文件
            if (videoFile != null && videoFile.exists()) {
                videoFile.delete();
                log.info("失败时清理临时视频文件: {}", videoFile.getAbsolutePath());
            }
            
            // 更新失败状态
            record.setConversionStatus(ConversionStatus.FAILED.getCode());
            record.setErrorMessage(e.getMessage());
            record.setUpdateTime(LocalDateTime.now());
            conversionRecordMapper.updateById(record);
            
            throw new BusinessException(ErrorCode.CONVERSION_FAILED, e.getMessage(), e);
        } finally {
            if (acquired) {
                CONVERT_SEMAPHORE.release();
            }
        }
    }
    
    @Override
    public ConversionVideoToGifResponseDTO getConversionRecord(Long id) {
        // 先尝试从缓存获取
        ConversionVideoToGifResponseDTO cachedResponse = cacheService.getCachedConversionStatus(id);
        if (cachedResponse != null) {
            log.debug("从缓存返回转换记录: id={}", id);
            return cachedResponse;
        }
        
        // 缓存未命中，从数据库查询
        ConversionRecordVideoToGif record = conversionRecordMapper.selectById(id);
        if (record == null) {
            throw new BusinessException(ErrorCode.CONVERSION_RECORD_NOT_EXISTS);
        }
        
        ConversionVideoToGifResponseDTO response = buildResponseDTO(record);
        
        // 缓存结果
        // 进行中的任务缓存时间短一些（30秒），已完成/失败的缓存时间长一些（10分钟）
        long ttl = ConversionStatus.CONVERTING.getCode().equals(record.getConversionStatus()) ? 30 : 600;
        cacheService.cacheConversionStatus(id, response, ttl);
        
        return response;
    }
    
    @Override
    public List<ConversionVideoToGifResponseDTO> getRecentConversions(int limit) {
        LambdaQueryWrapper<ConversionRecordVideoToGif> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(ConversionRecordVideoToGif::getCreateTime)
                .last("LIMIT " + limit);
        
        List<ConversionRecordVideoToGif> records = conversionRecordMapper.selectList(queryWrapper);
        return records.stream()
                .map(this::buildResponseDTO)
                .collect(Collectors.toList());
    }
    
    @Override
    public void deleteConversion(Long id) {
        ConversionRecordVideoToGif record = conversionRecordMapper.selectById(id);
        if (record == null) {
            throw new BusinessException(ErrorCode.CONVERSION_RECORD_NOT_EXISTS);
        }
        
        // 删除文件（自动判断COS或本地）
        cosFileUtils.deleteFile(record.getOriginalFilePath());
        cosFileUtils.deleteFile(record.getGifFilePath());
        cosFileUtils.deleteFile(record.getThumbnailPath());
        
        // 删除记录
        conversionRecordMapper.deleteById(id);
        
        // 删除缓存
        cacheService.removeCachedConversionStatus(id);
        
        log.info("删除转换记录: {}", id);
    }
    
    /**
     * 构建响应DTO
     */
    private ConversionVideoToGifResponseDTO buildResponseDTO(ConversionRecordVideoToGif record) {
        ConversionVideoToGifResponseDTO dto = new ConversionVideoToGifResponseDTO();
        BeanUtil.copyProperties(record, dto);
        
        // 🔑 确保尺寸信息正确复制（BeanUtil可能因为类型不匹配而失败）
        if (record.getGifWidth() != null) {
            dto.setGifWidth(record.getGifWidth());
        }
        if (record.getGifHeight() != null) {
            dto.setGifHeight(record.getGifHeight());
        }
        
        log.debug("构建响应DTO - 记录ID: {}, thumbnailFilename: {}, 尺寸: {}x{}", 
                record.getId(), record.getThumbnailFilename(), dto.getGifWidth(), dto.getGifHeight());
        
        // 构建GIF访问URL（自动处理COS URL和本地路径）
        if (record.getGifFilePath() != null) {
            String gifFilePath = record.getGifFilePath();
            String gifUrl;
            
            // 如果是临时路径（异步上传中），返回本地文件代理URL
            if (gifFilePath.startsWith("temp://")) {
                String localPath = gifFilePath.substring(7);  // 移除 "temp://" 前缀
                // 使用本地文件代理URL，前端可以立即访问
                gifUrl = serverDomain + "/api/files/local?path=" + URLEncoder.encode(localPath, StandardCharsets.UTF_8);
                log.info("构建GIF临时访问URL（异步上传中）: {}, 本地路径: {}", gifUrl, localPath);
            } else {
                // 正常COS URL或本地路径
                String rawUrl = cosFileUtils.getFileUrl(gifFilePath, serverDomain + "/api/files/");
                
                // 🔑 如果是COS URL，使用代理URL避免跨域问题
                if (rawUrl != null && (rawUrl.startsWith("http://") || rawUrl.startsWith("https://"))) {
                    // COS URL，构建代理URL用于预览
                    try {
                        // 🔑 验证URL不为空
                        if (rawUrl.trim().isEmpty()) {
                            log.warn("构建GIF代理URL失败：rawUrl为空，使用原始URL");
                            gifUrl = rawUrl;
                        } else {
                            String encodedUrl = URLEncoder.encode(rawUrl, StandardCharsets.UTF_8);
                            // 🔑 验证编码后的URL不为空
                            if (encodedUrl == null || encodedUrl.trim().isEmpty()) {
                                log.warn("构建GIF代理URL失败：编码后的URL为空，使用原始URL: {}", rawUrl);
                                gifUrl = rawUrl;
                            } else {
                                gifUrl = serverDomain + "/api/files/proxy?url=" + encodedUrl;
                                log.info("构建GIF代理URL: {}, 原始COS URL: {}", gifUrl, rawUrl);
                            }
                        }
                    } catch (Exception e) {
                        log.warn("构建GIF代理URL失败，使用原始URL: {}", rawUrl, e);
                        gifUrl = rawUrl;
                    }
                } else {
                    // 本地路径，使用本地文件代理URL
                    gifUrl = serverDomain + "/api/files/local?path=" + URLEncoder.encode(rawUrl, StandardCharsets.UTF_8);
                    log.info("构建GIF本地文件代理URL: {}, 本地路径: {}", gifUrl, rawUrl);
                }
            }
            
            dto.setGifUrl(gifUrl);
            log.info("GIF访问URL已设置: {}, 文件大小: {}, 尺寸: {}x{}", 
                    gifUrl, record.getGifFileSize(), record.getGifWidth(), record.getGifHeight());
        }
        
        // 构建缩略图访问URL（自动处理COS URL和本地路径）
        if (record.getThumbnailPath() != null) {
            String thumbnailUrl = cosFileUtils.getFileUrl(record.getThumbnailPath(), serverDomain + "/api/files/");
            dto.setThumbnailUrl(thumbnailUrl);
            log.info("构建缩略图访问URL: {}, 文件路径: {}", thumbnailUrl, record.getThumbnailPath());
        };
        
        return dto;
    }
}




